# 示例代码
# 将摄像头采集的人脸作为模型训练参数，训练了人脸识别模型，并将模型数据输出为train.yml文件
import numpy as np
import cv2 as cv
import time

cam = cv.VideoCapture(0) # 实例化摄像头图片流

def main():
  faces=get_faces_fromCam(20) # 通过摄像头采集人脸图像，作为人脸识别模型训练素材
  labels = np.zeros(len(faces), int) # 创建一组数据类型为 int 的标签组(内容为0)
  labels_other = np.ones(len(faces), int)
  # 由于人脸检测后返回的人脸图像大小不一致，需要统一大小(在主程序中处理)，由于都是矩形，可使用缩放的方式可确保图像不发生形变
  max = 0 # 存储最大特征值正方向图片的边长
  other = []
  _, face_other = face_detect(cv.imread('other_man.jpg'))
  for i in faces:
    if(np.size(i[0]) > max):
      max = np.size(i[0])
    other.append(face_other)

  # 将小图像都进行放大
  for i in range(len(faces)):
    faces[i] = cv.resize(faces[i], (max,max), interpolation=cv.INTER_LANCZOS4)
    other[i] = cv.resize(other[i], (max,max), interpolation=cv.INTER_LANCZOS4)

  # 拼接人脸特征图和标签，x和y数组下标一一对应（特征与标签一一对应）
  x=np.concatenate((faces,other),axis=0)
  y=np.concatenate((labels,labels_other),axis=0)

  index = [i for i in range(len(y))] # 创建一个内容为[0,1,2,...,len(y)-1]的列表
  np.random.seed(1) # 产生一组随机数序列
  np.random.shuffle(index) # 打乱 index 列表内容顺序，可以获得一个对 人脸特征图和标签 数组的乱序下标
  #获得打乱顺序的训练数据和训练标签，避免训练数据的顺序对模型的影响，增加模型的泛化能力。
  train_data = x[index]
  train_label = y[index]

  recognizer = cv.face.LBPHFaceRecognizer.create() # 创建训练器
  recognizer.train(train_data, train_label) # 训练数据
  # 保存训练数据
  recognizer.write('train.yml')
# 人脸检测
# image: 待检测图像
# return: 脸部部分图像, 图像在原图的坐标宽高(x,y,w,h)信息元组
def face_detect(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) # 取灰度图像
    face_detector = cv.CascadeClassifier("/home/pi/.local/lib/python3.9/site-packages/cv2/data/haarcascade_frontalface_default.xml") # 用默认人脸模型构建级联分类器
    faces = face_detector.detectMultiScale(gray, 1.2, 6) # 获取检测到的所有人脸位置信息
    # 如果未检测到面部，则返回原始图像
    if (len(faces) == 0):
        return None, None
    # 目前只取图像的一张脸，xy为矩形左上角坐标，wh为矩形的宽高(因为返回是正方形，所以w=h)
    (x, y, w, h) = faces[0]
    # 返回图像的脸部部分
    return faces[0], gray[y:y + h, x:x + w]

# 从摄像头获取人脸图片，并将有人脸信息的图像存入列表返回
def get_faces_fromCam(num=20):
  faces=[]
  counter = 0
  temp_time = time.time()
  while(True): # 循环采集摄像头内图片，找到人脸
    _, img = cam.read()
    rect, face = face_detect(img)
    # 注意：人脸检测后返回的图像大小不一致，需要统一大小(在主程序中处理)
    # 我们忽略未检测到的脸部
    if (face is not None) and time.time()-temp_time > 0.01:
      temp_time = time.time()
      counter+=1
      # 将脸添加到脸部列表并添加相应的标签
      faces.append(face)

      # 因为无显示器，不做显示图片
      cpimg = cv.rectangle(img, (rect[0], rect[1]), (rect[0]+rect[2], rect[1]+rect[3]), (0,255,0)) # 画矩形
      cv.imshow('img', cpimg)
      print(counter)
      time.sleep(0.001)
      if(counter == num):
         break
    cv.imshow('img', img)
    key = cv.waitKey(1)
    if key == ord('q'):
      break
  return faces

if __name__ == '__main__':
  print('main')
  main()
